#include "SWFOutputStream.h"

#define SIZE_ALLOC 1024 * 1024

SWFOutputStream::SWFOutputStream()
	: _position(0)
	, bitPos(8)
	, currentByte(0)
{

}

SWFOutputStream::~SWFOutputStream()
{

}

void SWFOutputStream::writeUI8( UI8 v)
{
	size_t size = sizeof(UI8);
	checkSize(size);
	_byteArray.copy((char*)&v, size, _position);
	_position += size;
}

void SWFOutputStream::writeUI16( UI16 v )
{
	size_t size = sizeof(UI16);
	checkSize(size);
	_byteArray.copy((char*)&v, size, _position);
	_position += size;
}

void SWFOutputStream::writeUI32( UI32 v )
{
	size_t size = sizeof(UI32);
	checkSize(size);
	_byteArray.copy((char*)&v, size, _position);
	_position += size;
}

void SWFOutputStream::writeU8( UI8 v )
{
	writeUI8(v);
}

void SWFOutputStream::writeU30( UI30 v )
{
	while (true)
	{
		UI8 bitValue = v & 0x7f;//0111 1111
		v = v >> 7;
		if (v > 0)
		{
			bitValue = bitValue | 0x80; //1000 0000
		}
		this->writeUI8(bitValue);
		if (v == 0) break;
	}
}

void SWFOutputStream::writeU32( UI32 v )
{
	while (true)
	{
		UI8 bitValue = v & 0x7f;//0111 1111
		v = v >> 7;
		if (v > 0)
		{
			bitValue = bitValue | 0x80; //1000 0000
		}
		this->writeUI8(bitValue);
		if (v == 0) break;
	}
}

void SWFOutputStream::writeSI8( SI8 v )
{
	size_t size = sizeof(SI8);
	checkSize(size);
	_byteArray.copy((char*)&v, size, _position);
	_position += size;
}

void SWFOutputStream::writeSI16( SI16 data )
{
	this->writeUI16(data);
}

void SWFOutputStream::writeS24( SI24 )
{
	assert(false);
}

void SWFOutputStream::writeS32( SI32 v )
{
	this->writeUI8(v & 0xff);
	this->writeUI8((v >> 8) & 0xff);
	this->writeUI8((v >> 16) & 0xff);
	this->writeUI8((v >> 24) & 0xff);
}

void SWFOutputStream::writeByte(char data)
{
	size_t size = sizeof(char);
	checkSize(size);
	_byteArray.copy(&data, size, _position);
	_position += size;
}

void SWFOutputStream::writeBits(int data, UI32 size)
{
	while (size > 0)
	{
		if (size > bitPos)
		{
			int v = data << (32 - size) >> (32 - bitPos);
			int mask = 0xffffffff >> (32 - bitPos);
			//if more bits left to write than shift out what will fit
			currentByte |= v & mask;

			// shift all the way left, then right to right
			// justify the data to be or'ed in
			this->writeByte(currentByte);
			size -= bitPos;
			currentByte = 0;
			bitPos = 8;
		}
		else // if (size <= bytePos)
		{
			int v = data << (32 - size) >> (32 - bitPos);
			int mask = 0xffffffff >> (32 - bitPos);
			//if more bits left to write than shift out what will fit
			currentByte |= v & mask;
			bitPos -= size;
			size = 0;

			if (bitPos == 0)
			{
				//if current byte is filled
				this->writeByte(currentByte);
				currentByte = 0;
				bitPos = 8;
			}
		}
	}
}

void SWFOutputStream::writeUB( UB data, UI32 size )
{
	this->writeBits((int)data, size);
}

void SWFOutputStream::writeSB( SB data, UI32 size )
{
	//assert (data >= -(1<<(size-1)) && data <= (1<<(size-1))-1);
	writeBits(data, size);
}

void SWFOutputStream::writeFB( FB )
{
	assert(false);
}

void SWFOutputStream::writeFixed(Fixed value)
{
	SI32  v = value * 65535;
	this->writeS32(v);
}

void SWFOutputStream::writeFixed8(Fixed8 value)
{
	UI16 val = (UI16)(value * 256.0f);
	this->writeUI16(val);
}

void SWFOutputStream::writeFloat( FLOAT f )
{
	assert(false);
}

void SWFOutputStream::writeDouble( DOUBLE v )
{
	size_t size = sizeof(DOUBLE);
	checkSize(size);
	_byteArray.copy((char*)&v, size, _position);
	_position += size;
}

void SWFOutputStream::writeBytes( const char* bytes, size_t len )
{
	checkSize(len);
	_byteArray.copy(bytes, len, _position);
	_position += len;
}

void SWFOutputStream::writeString( std::string s )
{
	size_t len = s.length() + 1;
	writeBytes(s.c_str(), len);
}

void SWFOutputStream::writeUTFString( std::string s )
{
	size_t len = s.length();
	writeU30(len);
	writeBytes(s.c_str(), len);
}

void SWFOutputStream::checkSize(size_t v)
{
	_byteArray.resize(v + _position);
}

void SWFOutputStream::writeRect( const Rect& rect )
{
	this->writeUB(rect.NBits, 5);
	this->writeSB(rect.Xmin, rect.NBits);
	this->writeSB(rect.Xmax, rect.NBits);
	this->writeSB(rect.Ymin, rect.NBits);
	this->writeSB(rect.Ymax, rect.NBits);
	this->alignBits();
}

void SWFOutputStream::alignBits()
{
	if (bitPos != 8)
		this->writeByte(currentByte);
	bitPos = 8;
	currentByte = 0;
}

void SWFOutputStream::writeByteArray( ByteArray* ba )
{
	this->writeBytes(ba->getData(), ba->getLength());
}

ByteArray* SWFOutputStream::getByteArray()
{
	return &_byteArray;
}

void SWFOutputStream::reset()
{
	bitPos = 8;
	currentByte = 0;
	_position = 0;
	_byteArray.clear();
}

void SWFOutputStream::writeColorRGB( const ColorRGB& color )
{
	UI8 b = color & 0xff;
	UI8 g = (color >> 8) & 0xff;
	UI8 r = (color >> 16) & 0xff;
	
	this->writeUI8(r);
	this->writeUI8(g);
	this->writeUI8(b);
}

void SWFOutputStream::writeColorRGBA( const ColorRGB& color )
{
	UI8 a = color & 0xff;
	UI8 b = (color >> 8) & 0xff;
	UI8 g = (color >> 16) & 0xff;
	UI8 r = (color >> 24) & 0xff;

	this->writeUI8(r);
	this->writeUI8(g);
	this->writeUI8(b);
	this->writeUI8(a);
}

void SWFOutputStream::writeMatrix( const Matrix& matrix )
{
	this->writeUB(matrix.HasScale, 1);
	if (matrix.HasScale)
	{
		this->writeUB(matrix.NScaleBits, 5);
		this->writeSB(matrix.ScaleX, matrix.NScaleBits);
		this->writeSB(matrix.ScaleY, matrix.NScaleBits);
	}

	this->writeUB(matrix.HasRotate, 1);
	if (matrix.HasRotate)
	{
		this->writeUB(matrix.NRotateBits, 5);
		this->writeSB(matrix.RotateSkew0, matrix.NRotateBits);
		this->writeSB(matrix.RotateSkew1, matrix.NRotateBits);
	}

	this->writeUB(matrix.NTranslateBits, 5);
	this->writeSB(matrix.TranslateX, matrix.NTranslateBits);
	this->writeSB(matrix.TranslateY, matrix.NTranslateBits);
	this->alignBits();
}

void SWFOutputStream::writeColorTransformWithAlpha( const CXFORMWITHALPHA* ct )
{
	this->writeUB(ct->HasAddTerms, 1);
	this->writeUB(ct->HasMultTerms, 1);
	this->writeUB(ct->Nbits, 4);

	if (ct->HasMultTerms == 1)
	{
		this->writeSB(ct->RedMultTerm, ct->Nbits);
		this->writeSB(ct->GreenMultTerm, ct->Nbits);
		this->writeSB(ct->BlueMultTerm, ct->Nbits);
		this->writeSB(ct->AlphaMultTerm, ct->Nbits);
	}
	if (ct->HasAddTerms == 1)
	{
		this->writeSB(ct->RedAddTerm, ct->Nbits);
		this->writeSB(ct->GreenAddTerm, ct->Nbits);
		this->writeSB(ct->BlueAddTerm, ct->Nbits);
		this->writeSB(ct->AlphaAddTerm, ct->Nbits);
	}
	this->alignBits();
}
